home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1996 November / macformat-043.iso / mac / Shareware Plus / Developers / Sprite Animation Toolkit 2.3.8 / Demos / OffscreenToys SAT demo ƒ / OffscreenToysSAT.p < prev    next >
Encoding:
Text File  |  1996-05-23  |  25.1 KB  |  804 lines  |  [TEXT/PJMM]

  1. {--------- OFFSCREEN TOYS with SAT ---------}
  2. {by Ingemar Ragnemalm 1994}
  3.  
  4. {Offscreen Toys is a nice little demo I made to make a SAT-like demo with complete source}
  5. {code, independent of any libraries (except Apple's). Now, this should be easier to do with}
  6. {SAT, right? Well, partially so, but while adapting it to SAT, I ran into a minor flaw that}
  7. {SAT had (and doesn't have from version 2.1 and up), namely that drawing took place after}
  8. {moving sprites, but before checking for collisions, which didn't look too good in programs}
  9. {where sprites bounce off each other. After fixing this flaw, I would say that the SAT}
  10. {version is indeed a bit better than the independent version. The result is quite a bit faster}
  11. {and with asynch sound.}
  12. {}
  13. {As yet another SAT demo, what does it give us?}
  14. {• If you wonder what it costs to have a real event loop, or good collision handling, this demo}
  15. {shows that pretty well.}
  16. {• Demonstrates a moveable window AND fast mode at the same time, with the precautions}
  17. {that demands when moving the window. (The window mustn't be moved outside the screen,}
  18. {some internal SAT variables – that you otherwise should never care about – must be adjusted,}
  19. {and we must stay word-aligned to make it work in b/w).}
  20. {}
  21. {I don't consider this demo final in any way. Known flaws:}
  22. {• I have made some mistakes in the port-setting. Nothing fatal, I think. FIXED.}
  23. {• I don't protect the user from moving the window outside the screen, which might be fatal}
  24. {when the SAT blitters are turned on. FIXED.}
  25. {• Like in the "real" Offscreen Toys, there is a bug that causes sprites to disappear for a short}
  26. {while, since the position gets negative. No big deal.}
  27. {• The code for the marble should be separated from the main program, to make the code easier}
  28. {to follow.}
  29. {I'll fix those things when I find time and inspiration for it - but you are welcome to do it if}
  30. {you want!}
  31.  
  32. program OffscreenToysSAT;
  33.     uses
  34. {$ifc UNDEFINED THINK_PASCAL}
  35.         Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, {PackIntf,}
  36.         TextEdit, Traps, {Desk,}
  37.         Devices, Memory, SegLoad, Scrap, ToolUtils, {OSEvents,}
  38.         OSUtils, Menus, Resources, StandardFile, GestaltEqu, Files, Errors, 
  39. {$elsec}
  40.         InterfacesUI, 
  41. {$endc}
  42.         SAT;
  43.  
  44. { --- PART 1: Variables and constants: -----------------------------------------}
  45.  
  46. {A redefined sprite record:}
  47.     type
  48.         OTSpritePtr = ^OTSprite;
  49.         OTSprite = record
  50. { Variables that you should change as appropriate }
  51.                 kind: Integer; { Used for identification. >0: friend. <0 foe }
  52.                 position: Point;
  53.                 hotRect, hotRect2: Rect; { Tells how large the sprite is; hotRect is centered around origo }
  54.                                         {hotRect is set by you. hotRect2 is offset to the current position.}
  55.                 face: FacePtr; { Pointer to the Face (appearance) to be used. }
  56.                 task: ProcPtr; { Callback-routine, called once per frame. If task=nil, the sprite is removed. }
  57.                 hitTask: ProcPtr; { Callback in collisions. }
  58.                 destructTask: ProcPtr; { Called when a sprite is disposed. (Usually nil.) }
  59.                 clip: RgnHandle; {Clip region to be used when this sprite is drawn.}
  60. { SAT variables that you shouldn't change: }
  61.                 oldpos: Point;                {The 'task' routine is not allowed to change this! }
  62.                 next, prev: SpritePtr;    {You may change them in your own sorting routine, but be careful if you do.}
  63.                 r, oldr: Rect;                {Rectangle telling where to draw. Avoid messing with it.}
  64.                 oldFace: FacePtr;            {Used by RunSAT2}
  65.                 dirty: Boolean;            {Used by RunSAT2}
  66. {Variables for internal use by the sprites. Use as you please. Edit as necessary - this is merely a default}
  67. {set, enough space for most cases - but if you change the size of the record, call SetSpriteSize immediately}
  68. {after initializing (before any sprites are created)!}
  69.                 layer: integer; {For layer-sorting. When not used for that, use freely.}
  70.                 speed: Point; { Can be used for speed, but not necessarily. }
  71.                 mode: integer; { Usually used for different modes and/or to determine what image to show next. }
  72.                 fixedPos: Point; {Fixed-point position}
  73.                 appLong: Longint; {Longint for free use by the application.}
  74.             end;
  75.  
  76.     const
  77.         kAppleID = 128;
  78.         kFileID = 129;
  79.         kMBarHeight = 20;    { We assume 20 pixels menu bar for window sizing and dragging.}
  80.  
  81.         kWindID = 128;            { Window resource ID }
  82.         kAboutAlertID = 128;    { Alert resource ID }
  83.  
  84.         kSpriteNumber = 5;        { Number of moving objects }
  85.     var
  86.         gColorQDFlag: Boolean;        {True if 32-bit QD exists. If not, we run everything in b/w.}
  87.         gHasWNE: Boolean;        {True if we can use WaitNextEvent}
  88.         gSoundFlag: Boolean;        {True if we want sound.}
  89.         gFast: Boolean;
  90.  
  91.         gWhoa: Boolean;            {True when we want to quit}
  92.         gCollisionFlag: Boolean;    {Collisions or not?}
  93.  
  94. {Menu handles}
  95.         appleMenu, fileMenu: MenuHandle;
  96.  
  97. {The window we'll be using}
  98.         gWind: WindowPtr;
  99.  
  100. {Our two offscreens:}
  101. {offScreen, backScreen: GrafPtr;}
  102.  
  103. {A cicn loded as a face}
  104.         gCicn: FacePtr;
  105.         kgck: Handle;
  106.  
  107. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  108. {SAT, and a lot more information for each, but here we want it *simple*.}
  109. {- position: The positions in local coordinates for the window}
  110. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  111. {- speed: Speed vectors that is added to fixedPos for every frame}
  112. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  113. {position, fixedPos: array[1..kSpriteNumber] of Point;}
  114. {speed: array[1..kSpriteNumber] of Point;}
  115. {r: array[1..kSpriteNumber] of Rect;}
  116. {}
  117. {…but, since this now uses SAT, the linked list is built-in, so all the variables above are in}
  118. {the sprite record!}
  119.  
  120. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  121.  
  122.         gBowlSize: Longint;
  123.  
  124. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  125.  
  126. {BailOut: Emergency exit. We go here on most errors. Real programs report what the}
  127. {problem is. You may wish to put a breakpoint in BailOut when debugging.}
  128.  
  129.     procedure BailOut;
  130.     begin
  131.         SysBeep(1); {Minimal error message. Use alert in real programs.}
  132.         halt;
  133.     end;
  134.  
  135. {Several functions deleted since SAT handles what they do.}
  136.  
  137. { --- PART 3: Application specific routines: ---------------------------------}
  138.  
  139. {mouse clicks, keydowns, background tasks and update events: This is where all}
  140. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  141. {use this demo as application shell.}
  142.  
  143. {Mouse click in window content}
  144.  
  145.     procedure DoMouse (where: Point; modifiers: Longint);
  146.     begin
  147.     end;
  148.  
  149. {Keydown.}
  150.  
  151.     procedure DoKey (theKey: Char; modifiers: Longint);
  152.     begin
  153.     end;
  154.  
  155.     const
  156.         kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  157.         kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  158.  
  159.  
  160. {A rather boring subroutine that moves the sprites i and j away from each other.}
  161. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  162. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  163. {objects, I'd be happy to put it in.}
  164. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  165. {value as reuseable code - depends on your application.}
  166.     procedure Separate (i, j: OTSpritePtr);
  167.         var
  168.             initVector, nowVector: Point;
  169.             absH, absV: integer;
  170.             moveH, moveV: integer;
  171.             frac: integer;
  172. {Normal signum function (which I don't think is in the libs)}
  173.         function Sgn (x: integer): integer;
  174.         begin
  175.             if x > 0 then
  176.                 Sgn := 1
  177.             else if x < 0 then
  178.                 Sgn := -1
  179.             else
  180.                 Sgn := 0;
  181.         end;
  182.  
  183.     begin {Separate}
  184.         frac := 0;
  185.         initVector.h := i^.position.h - j^.position.h;
  186.         initVector.v := i^.position.v - j^.position.v;
  187.         absH := abs(initVector.h);
  188.         absV := abs(initVector.v);
  189.         moveH := Sgn(initVector.h);
  190.         moveV := Sgn(initVector.v);
  191.         if moveH = 0 then
  192.             if moveV = 0 then
  193.                 moveV := 1;
  194.         repeat
  195.             if absH > absV then
  196.                 begin
  197.                     i^.position.h := i^.position.h + moveH;
  198.                     j^.position.h := j^.position.h - moveH;
  199.                     frac := frac + absV;
  200.                     if frac > absH then
  201.                         begin
  202.                             i^.position.v := i^.position.v + moveV;
  203.                             j^.position.v := j^.position.v - moveV;
  204.                             frac := frac - absH;
  205.                         end
  206.                 end
  207.             else
  208.                 begin
  209.                     i^.position.v := i^.position.v + moveV;
  210.                     j^.position.v := j^.position.v - moveV;
  211.                     frac := frac + absH;
  212.                     if frac > absV then
  213.                         begin
  214.                             i^.position.h := i^.position.h + moveH;
  215.                             j^.position.h := j^.position.h - moveH;
  216.                             frac := frac - absV;
  217.                         end
  218.                 end;
  219.             nowVector.h := i^.position.h - j^.position.h;
  220.             nowVector.v := i^.position.v - j^.position.v;
  221.         until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  222.         i^.fixedPos.h := BSL(i^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  223.         i^.fixedPos.v := BSL(i^.position.v, 4);
  224.  
  225.         j^.fixedPos.h := BSL(j^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  226.         j^.fixedPos.v := BSL(j^.position.v, 4);
  227.  
  228.     end;{Separate}
  229.  
  230. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  231. {orthogonal to it.}
  232.     procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  233.         var
  234.             l2, v1pr: Longint;
  235.     begin
  236. {parallell := direction * (v1 DOT direction) /|direction|**2}
  237. {normal := v1 - parallell}
  238.  
  239.         l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  240.         v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  241.  
  242.         parallell.h := direction.h * v1pr div l2;
  243.         parallell.v := direction.v * v1pr div l2;
  244.         normal.h := v1.h - parallell.h;
  245.         normal.v := v1.v - parallell.v;
  246.     end; {SplitVector}
  247.  
  248.     procedure HandleMarble (i: OTSpritePtr);
  249.         var
  250.             vector: Point;
  251.     begin
  252. {Modify fixed-point position by speed}
  253.         i^.fixedPos.h := i^.fixedPos.h + i^.speed.h;
  254.         i^.fixedPos.v := i^.fixedPos.v + i^.speed.v;
  255.  
  256. {Make position by shifting away the 4 binary "decimals"}
  257.         if i^.fixedPos.h >= 0 then
  258.             i^.position.h := BSR(i^.fixedPos.h, 4)
  259.         else
  260.             i^.position.h := BitOr(BSR(i^.fixedPos.h, 4), $f000);
  261.         if i^.fixedPos.v >= 0 then
  262.             i^.position.v := BSR(i^.fixedPos.v, 4)
  263.         else
  264.             i^.position.v := BitOr(BSR(i^.fixedPos.v, 4), $f000);
  265.  
  266. {Outside the allowed area?}
  267.         if i^.position.h < 0 then
  268.             begin
  269.                 i^.speed.h := abs(i^.speed.h) * kWallBounce div 10 + 1;
  270. {     i^.position.h := 0;}
  271.             end;
  272.         if i^.position.v < 0 then
  273.             begin
  274.                 i^.speed.v := abs(i^.speed.v) * kWallBounce div 10 + 1;
  275. { i^.position.v := 0; }
  276.             end;
  277.         if i^.position.h + gCicn^.iconMask.bounds.right > gSAT.offScreen.port^.portRect.right then
  278.             begin
  279.                 i^.speed.h := -abs(i^.speed.h) * kWallBounce div 10 - 1;
  280. { i^.position.h := gSAT.offScreen^.portRect.right - gCicn^.iconMask.bounds.right; }
  281.             end;
  282.         if i^.position.v + gCicn^.iconMask.bounds.bottom > gSAT.offScreen.port^.portRect.bottom then
  283.             begin
  284.                 i^.speed.v := -abs(i^.speed.v) * kWallBounce div 10 - 1;
  285. { i^.position.v := gSAT.offScreen^.portRect.bottom - gCicn^.iconMask.bounds.bottom; }
  286.             end;
  287.  
  288. {Are we in the bowl? If we are, accelerate towards the center.}
  289.         vector.h := i^.position.h + 16 - BSR(gSAT.offScreen.port^.portRect.right, 1);
  290.         vector.v := i^.position.v + 16 - BSR(gSAT.offScreen.port^.portRect.bottom, 1);
  291.         if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  292.             begin
  293.                 i^.speed.h := i^.speed.h - vector.h div 2;
  294.                 i^.speed.v := i^.speed.v - vector.v div 2;
  295.             end;
  296.     end; {position/speed loop}
  297.  
  298.     procedure HitMarble (i, j: OTSpritePtr);
  299.         var
  300.             vector: Point;
  301.             squaredLength: Longint;
  302.             p1, p2, n1, n2, tmpSpeed: Point;
  303.     begin
  304.         if not gCollisionFlag then
  305.             exit(HitMarble);
  306.  
  307. {Find the vector between them}
  308.         vector.h := i^.position.h - j^.position.h;
  309.         vector.v := i^.position.v - j^.position.v;
  310.         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  311. {If it is shorter than the diameter of a ball…}
  312.         if squaredLength < kBallDiameterSquared then
  313.             begin
  314. {Move them away from each other}
  315.                 Separate(i, j);
  316.  
  317. {if false then}
  318.                 begin
  319. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  320. {nice and realistic bounces)}
  321.                     SplitVector(i^.speed, vector, p1, n1);
  322.                     SplitVector(j^.speed, vector, p2, n2);
  323.  
  324.                     j^.speed.h := p1.h + n2.h;
  325.                     j^.speed.v := p1.v + n2.v;
  326.  
  327.                     i^.speed.h := p2.h + n1.h;
  328.                     i^.speed.v := p2.v + n1.v;
  329.                 end;
  330.  
  331. {Old Offscreen Toys just swapped the speed, as commented out below. This is not as realistic.}
  332. {tmpSpeed := i^.speed;}
  333. {i^.speed := j^.speed;}
  334. {j^.speed := tmpSpeed;}
  335.  
  336. {Play a sound. SAT is good at playing sounds!}
  337.                 if gSoundFlag then
  338.                     SATSoundPlay(kgck, 1, false);
  339.  
  340.             end;
  341.     end; {collision loop}
  342.  
  343.     procedure SetupMarble (i: OTSpritePtr);
  344.     begin
  345.         i^.fixedPos.h := BSL(i^.position.h, 4);
  346.         i^.fixedPos.v := BSL(i^.position.v, 4);
  347.         i^.speed.h := Random mod 32;
  348.         i^.speed.v := Random mod 32;
  349.         i^.task := @HandleMarble;
  350.         i^.hitTask := @HitMarble;
  351.         i^.face := gCicn;
  352.         SetRect(i^.hotRect, 0, 0, 32, 32);
  353.     end;
  354.  
  355. {A static variable only used in DrawBackground}
  356.     var
  357.         thePat: PixPatHandle;
  358.  
  359.     procedure DrawBackground;
  360.         const
  361.             patID = 128;
  362.         var
  363.             r: Rect;
  364.             mycolorFlag: Boolean;
  365.  
  366. {A little routine for setting the forecolor with a single line.}
  367.         procedure OTForeColor (red, green, blue: integer);
  368.             var
  369.                 theColor: RGBColor;
  370.         begin
  371.             theColor.red := red;
  372.             theColor.green := green;
  373.             theColor.blue := blue;
  374.             RGBForeColor(theColor);
  375.         end;
  376.  
  377.         var
  378.             savePort: SATPort;
  379.  
  380.     begin {DrawBackground}
  381.         SATGetPort(savePort);
  382.  
  383.         SATSetPortBackScreen;
  384.  
  385. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  386.  
  387. {For drawing the background, let's make a local flag that tells us if we shold draw}
  388. {b/w patterns or color ones.}
  389.         if gColorQDFlag then
  390.             mycolorFlag := gSAT.initDepth > 1
  391.         else
  392.             mycolorFlag := false;
  393.  
  394.         if mycolorFlag then
  395.             begin
  396.                 if thePat = nil then
  397.                     thePat := GetPixPat(patID);
  398.                 PenPixPat(thePat)
  399.             end
  400.         else
  401.             begin
  402.                 if thePat = nil then
  403.                     thePat := PixPatHandle(GetResource('ppat', patID));
  404.                 PenPat(thePat^^.pat1Data);
  405.             end;
  406.         PaintRect(gSAT.backScreen.port^.portRect);
  407.         PenNormal;
  408.  
  409. {Then we draw some circles.}
  410.  
  411.         r := gSAT.backScreen.port^.portRect;
  412.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  413.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  414.         if mycolorFlag then
  415.             begin
  416.                 OTForeColor(-10000, -10000, -10000);
  417.                 PaintOval(r);
  418.             end
  419.         else
  420. {$IFC UNDEFINED THINK_PASCAL}
  421.             FillOval(r, qd.ltGray);
  422. {$ELSEC}
  423.         FillOval(r, ltGray);
  424. {$ENDC}
  425.  
  426.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  427.         if mycolorFlag then
  428.             begin
  429.                 OTForeColor(-25000, -25000, -25000);
  430.                 PaintOval(r);
  431.             end
  432.         else
  433. {$IFC UNDEFINED THINK_PASCAL}
  434.             FillOval(r, qd.gray);
  435. {$ELSEC}
  436.         FillOval(r, gray);
  437. {$ENDC}
  438.  
  439.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  440.         if mycolorFlag then
  441.             begin
  442.                 OTForeColor(20000, 20000, 20000);
  443.                 PaintOval(r);
  444.             end
  445.         else
  446. {$IFC UNDEFINED THINK_PASCAL}
  447.             FillOval(r, qd.dkGray);
  448. {$ELSEC}
  449.         FillOval(r, dkGray);
  450. {$ENDC}
  451.  
  452.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  453.         if gSAT.colorFlag then
  454.             OTForeColor(0, 0, 0);
  455.         PaintOval(r);
  456. {         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);}
  457. {        if mycolorFlag then}
  458. {            begin}
  459. {                OTForeColor(0, 0, 0);}
  460. {                PaintOval(r);}
  461. {            end}
  462. {        else}
  463. {            FillOval(r, black); }
  464.  
  465.         SATSetPortOffScreen;
  466.         CopyBits(gSAT.backScreen.port^.portBits, gSAT.offScreen.port^.portBits, gSAT.backScreen.port^.portRect, gSAT.backScreen.port^.portRect, srcCopy, nil);
  467.  
  468.         SATSetPort(savePort);
  469.     end; {DrawBackground}
  470.  
  471. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  472.  
  473. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  474. {the animation. However, it is hard to get high framerate then, since other programs}
  475. {(the Finder included) will process events which will make it less smooth.}
  476.  
  477.     procedure DoBackground;
  478.         var
  479.             tmpRect: Rect;
  480.             i, j: integer;
  481.             vector: Point;
  482.             saveGD: GDHandle;
  483.             savePort: GrafPtr;
  484.             tmpSpeed: Point;
  485.     begin {DoBackground}
  486.  
  487.         SATRun(gFast); {Eller konfigurerbart?}
  488.  
  489.     end; {DoBackground}
  490.  
  491.  
  492. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  493.  
  494. {Note to beginners: A program without update events processing is not a real Mac program!}
  495. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  496. {or worse, partially erased, which is really ugly.}
  497.  
  498.     procedure DoUpdate;
  499.         var
  500.             saveGD: GDHandle;
  501.             savePort: GrafPtr;
  502.     begin
  503.         if SATDepthChangeTest then
  504.             DrawBackground;
  505.         BeginUpdate(gWind);
  506.         SATRedraw;
  507.         EndUpdate(gWind);
  508.     end;
  509.  
  510. {DoAppleMenu and DoFileMenu: handle menu selections}
  511.  
  512.     procedure DoAppleMenu (item: integer);
  513.         var
  514.             str: Str255;
  515.             h: Handle;
  516.             savePort: SATPort;
  517.             ignore: integer;
  518.     begin
  519.         if item = 1 then
  520.             begin
  521.                 if Alert(kAboutAlertID, nil) = 1 then
  522.                     ; {Ignore result}
  523.             end
  524.         else
  525. {Apple menu other than "About": Code from TransSkel}
  526.             begin
  527.                 SATGetPort(savePort);
  528.                 GetMenuItemText(appleMenu, item, str);                    {Old intf: GetItem}
  529.                 SetResLoad(false);
  530.                 h := GetNamedResource('DRVR', str);
  531.                 SetResLoad(true);
  532.                 if h <> nil then
  533.                     begin
  534.                         ReserveMem(GetResourceSizeOnDisk(h) + $1000);    {Old intf: ResrvMem, SizeResource}
  535.                         ignore := OpenDeskAcc(str);
  536.                     end;
  537.                 SATSetPort(savePort);
  538.             end;
  539.     end; {DoAppleMenu}
  540.  
  541.     procedure DoFileMenu (item: integer);
  542.         var
  543.             start, finish, frames: Longint;
  544.             {fpsStr: Str255;}
  545.     begin
  546.         case item of
  547.             1:
  548. {Run animation without event processing until the user clicks the mouse}
  549. {Note: This runs the animation at maximum speed. In real programs, we}
  550. {must limit the speed with the system clock, e.g. inspect TickCount.}
  551.                 begin
  552.                     start := TickCount;
  553.                     frames := 0;
  554.                     while not Button do
  555.                         begin
  556.                             DoBackground;
  557.                             frames := frames + 1;
  558.                         end;
  559.                     finish := TickCount;
  560.                     SATReportStr(StringOf(frames * 60 div (finish - start):1, ' frames/second'));
  561.                 end;
  562.             2: 
  563.                 begin
  564.                     gCollisionFlag := not gCollisionFlag;
  565.                     CheckItem(fileMenu, 2, gCollisionFlag);
  566.                 end;
  567.             3: 
  568.                 begin
  569.                     gFast := not gFast;
  570.                     CheckItem(fileMenu, 3, gFast);
  571.                 end;
  572.             4: 
  573.                 begin
  574.                     gSoundFlag := not gSoundFlag;
  575.                     CheckItem(fileMenu, 4, gSoundFlag);
  576.                 end;
  577. {Set the flag that tells the program to quit.}
  578.             6: 
  579.                 gWhoa := true;
  580.         end; {case}
  581.     end; {DoFileMenu}
  582.  
  583. { --- PART 4: Event processing: -----------------------------------------}
  584.  
  585. {MenuSelection: Menu selection by mouse or command-key:}
  586.  
  587.     procedure MenuSelection (whatSelection: longInt);
  588.     begin
  589.         case HiWord(whatSelection) of
  590.             kAppleID: 
  591.                 DoAppleMenu(LoWord(whatSelection));
  592.             kFileID: 
  593.                 DoFileMenu(LoWord(whatSelection));
  594.         end; {case}
  595.         HiLiteMenu(0);
  596.     end;
  597.  
  598. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  599. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  600.  
  601.     procedure MainLoop;
  602.         const
  603.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  604.         var
  605.             hasEvent: Boolean;
  606.             theEvent: EventRecord;
  607.             theKey: Char;
  608.             whatSelection: Longint;
  609.             whichPart: integer;
  610.             whichWindow: WindowPtr;
  611.             r: rect;
  612.             p: Point;
  613.     begin
  614. {Get the next event. Use WaitNextEvent if possible.}
  615.         if gHasWNE then
  616.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  617.         else
  618.             begin
  619.                 SystemTask;
  620.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  621.             end;
  622.  
  623. {OK, so what happened then?}
  624.         if hasEvent then
  625.             case theEvent.what of
  626.                 mouseDown: 
  627.                     begin
  628.                         whichPart := FindWindow(theEvent.where, whichWindow);
  629.                         case whichPart of
  630.                             inMenuBar: 
  631.                                 begin
  632.                                     whatSelection := MenuSelect(theEvent.where);
  633.                                     MenuSelection(whatSelection);
  634.                                 end;
  635.                             inSysWindow: 
  636.                                 SystemClick(theEvent, whichWindow);
  637.                             inGoAway: 
  638.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  639.                                     gWhoa := true;
  640.                             inDrag: 
  641.                                 begin
  642.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  643.                                         SelectWindow(whichWindow);
  644.                                     r := gSAT.bounds;            {How big is the screen?}
  645. {Was: screenBits.bounds;{How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  646.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  647.  
  648.                                     InsetRect(r, 4, 4);
  649.  
  650. { LIMIT THE DRAGGING so no part of the window can get outside the screen! Cut down on r}
  651. {depending on where the click is? This is necessary if we use "fast mode", that is if we do}
  652. {SATRun(true) or any other operation with custom blitters.}
  653.                                     SetPort(whichWindow);
  654.                                     p := theEvent.where;
  655.                                     GlobalToLocal(p);
  656.                                     r.bottom := r.bottom - (whichWindow^.portRect.bottom - p.v);
  657.                                     r.left := r.left + p.h;
  658.                                     r.right := r.right - (whichWindow^.portRect.right - p.h);
  659.  
  660.                                     DragWindow(whichWindow, theEvent.where, r);
  661.  
  662.                                     if whichWindow = gSAT.wind.port then
  663.                                         SATWindMoved;
  664.  
  665.                                 end;
  666.                             inGrow: 
  667.                                 ;  {Ignored - we don't resize}
  668.                             inContent: 
  669.                                 if (whichWindow <> FrontWindow) then
  670.                                     SelectWindow(whichWindow)
  671.                                 else
  672.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  673.                         end; {case whichPart}
  674.                     end; {mouseDown}
  675.                 keyDown, autoKey: 
  676.                     begin
  677.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  678.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  679.                             MenuSelection(MenuKey(theKey))
  680.                         else
  681.                             DoKey(theKey, theEvent.modifiers);
  682.                     end;
  683.                 updateEvt:
  684. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  685.                     if WindowPtr(theEvent.message) = gWind then
  686.                         DoUpdate;
  687. {Handle disk inserts like TransSkel.}
  688.                 diskEvt: 
  689.                     if (HiWord(theEvent.message) <> noErr) then
  690.                         begin
  691.                             DILoad;
  692.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  693.                                 ;
  694.                             DIUnload;
  695.                         end; {diskEvt}
  696.                 otherwise {Other events are ignored}
  697.             end; {case}
  698.  
  699.         DoBackground;
  700.     end;
  701.  
  702. { --- PART 5: Initializations: -----------------------------------------}
  703.  
  704. {OTInit: Initialize global flags, menus and window}
  705.  
  706.     procedure OTInit;
  707.         const
  708. {Trap numbers}
  709.             _WaitNextEvent = $A860;
  710.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  711.             k32bQD = $AB1D;
  712.             _SndPlay = $A805;
  713.     begin
  714. {In case this isn't Think Pascal we have to make the standard inits ourselves.}
  715. {$IFC UNDEFINED THINK_PASCAL}
  716.         SATInitToolbox;
  717. {$ENDC}
  718.  
  719.         gHasWNE := SATTrapAvailable(_WaitNextEvent);
  720.         gColorQDFlag := SATTrapAvailable(k32bQD) and SATTrapAvailable(_GetCIcon); {???}
  721.         gWhoa := false;
  722.         gCollisionFlag := false;
  723.  
  724. {gSoundFlag := TrapAvailable(_SndPlay); – Let SAT decide if we CAN or not!}
  725.         gSoundFlag := true;
  726.  
  727. {What more should I check for? Check with Gestalt instead?}
  728.  
  729. {$IFC UNDEFINED THINK_PASCAL}
  730.         qd.randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  731. {$ELSEC}
  732.         randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  733. {$ENDC}
  734.  
  735. {Get the window, a color window if we are going to use color.}
  736.         if gColorQDFlag then
  737.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  738.         else
  739.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  740.  
  741. {Some menus. We could read these from resources.}
  742.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  743.         AppendMenu(appleMenu, 'About Offscreen Toys SAT…;(-');
  744.         AppendResMenu(appleMenu, 'DRVR');            {Old intf: AddResMenu}
  745.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  746.         fileMenu := NewMenu(kFileID, 'File');
  747.         AppendMenu(fileMenu, 'Try max speed;Collisions;Use SAT blitters;Use sound;(-;Quit/Q');
  748.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  749.         DrawMenuBar;
  750.         CheckItem(fileMenu, 4, gSoundFlag);
  751.     end;
  752.  
  753. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  754.  
  755.     procedure OTOffscreensInit;
  756.         var
  757.             savePort: SATPort;
  758.             r: Rect;
  759.             i: integer;
  760.             sp: SpritePtr;
  761.     begin {OTOffscreensInit}
  762.         SATConfigure(false, kNoSort, kForwardOneCollision, 32); {Only call *one* hitTask, not both.}
  763.         SATCustomInit(0, 0, gWind^.portRect, gWind, nil, false, false, false, true, false);
  764.  
  765.         DrawBackground;
  766.  
  767. {Done drawing!}
  768. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  769. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  770.  
  771.         SATSetPortScreen;
  772.  
  773. {Get the cicn resource}
  774. {Note: You can, of course, use several cicns and switch between.}
  775.         gCicn := SATGetFace(128);
  776.         kgck := SATGetNamedSound('Kgck');
  777.  
  778. {Initialize the sprites:}
  779.  
  780.         for i := 1 to kSpriteNumber do
  781.             sp := SATNewSprite(1, SATRand(gSAT.offScreen.port^.portRect.right - 32), (i - 1) * (gSAT.offScreen.port^.portRect.bottom - 32) div 5 + SATRand((gSAT.offScreen.port^.portRect.bottom - 32) div 5), @SetupMarble);
  782.  
  783.         if SATSoundInitChannels(2) < 2 then
  784.             SysBeep(1);
  785.     end;
  786.  
  787. { --- MAIN PROGRAM BODY: -----------------------------------------}
  788.  
  789. begin
  790.     OTInit;                    {General initializations}
  791.     OTOffscreensInit;        {Set up the offscreen grafports}
  792.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  793.     SATSetPortScreen;    {The front window is a good port to use.}
  794.  
  795. {Run until quit or click in the close box.}
  796.     repeat
  797.         SATSetPortScreen;    {The front window is a good port to use.}
  798.         MainLoop;
  799.     until gWhoa;
  800.  
  801. {No cleanup is necessary here.}
  802. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  803.     SATSoundShutup;
  804. end.